使用 Google Apps Script 同步 Markdown 筆記至 NotebookLM
TLDR
- NotebookLM 不支援直接同步 Markdown 檔案,且重複上傳會產生重複來源,無法批次更新。
- 透過 Google Apps Script (GAS) 可將本機 Markdown 檔案自動轉換並更新為 Google Docs,進而觸發 NotebookLM 的同步機制。
- 核心邏輯:利用 GAS 遞迴掃描來源資料夾,將 Markdown 內容寫入 Google Docs,並透過
getLastUpdated()判斷是否需要更新,確保 NotebookLM 的引用連結不中斷。 - 建議將同步邏輯封裝為 Library,並透過「時間驅動」觸發器達成自動化。
- 處理長文字時,必須使用分批寫入 (Chunking) 策略,避免超過 GAS 的執行限制。
NotebookLM 雖然具備強大的 RAG 能力,但對於習慣持續迭代 Markdown 筆記的使用者而言,其檔案管理機制存在明顯痛點:
- 重複上傳問題:上傳相同檔名的檔案會被視為全新來源,導致舊來源與新來源並存,無法自動覆蓋。
- 格式支援限制:不支援直接同步
.md檔案。 - 目錄結構繁瑣:無法批次選取子資料夾內的檔案進行同步。
為了解決這些問題,我們可以透過 Google Apps Script (GAS) 建立一個「中轉服務」,將 Markdown 轉換為 Google Docs,並利用 Google Drive 的同步機制與 NotebookLM 串接。
Google Apps Script 實作流程
1. 建立專案與設定參數
在 script.google.com 建立新專案後,將核心邏輯貼入 程式碼.gs。你需要設定來源與目標資料夾 ID,並定義黑白名單以過濾檔案。
javascript
function syncCloudyWingLog() {
const sourceFolderId = 'Source Folder Id';
const targetFolderId = 'Target Folder Id';
// 設定檔案白名單 (只同步 .md)
const whitelistFiles = [/\.md$/i];
// 設定檔案黑名單
const blacklistFiles = [/^index\.md$/i, /^about\.md$/i, /^tags\.md$/i];
SyncUtils.startSyncEngine(
sourceFolderId,
targetFolderId,
whitelistFiles,
blacklistFiles,
[],
[]
);
}2. 核心同步邏輯
為了確保 NotebookLM 的對話紀錄不中斷,我們必須維持 Google Doc 的檔案 ID 不變,僅在內容更新時進行覆寫。
javascript
/**
* 將單一檔案同步至 Google Doc
*/
function syncFileToGoogleDoc(sourceFile, targetFolder, targetDocName) {
const existingFiles = targetFolder.getFilesByName(targetDocName);
let targetDocFile = null;
while (existingFiles.hasNext()) {
const f = existingFiles.next();
if (f.getMimeType() === MimeType.GOOGLE_DOCS) {
targetDocFile = f;
break;
}
}
if (targetDocFile) {
// 僅在來源檔案更新時才寫入,避免不必要的 API 消耗
if (sourceFile.getLastUpdated() > targetDocFile.getLastUpdated()) {
updateDocContent(targetDocFile.getId(), sourceFile);
}
} else {
createDocContent(targetFolder, targetDocName, sourceFile);
}
}3. 分批寫入策略 (Chunking Strategy)
什麼情況下會遇到這個問題:當 Markdown 筆記內容過長,直接寫入會導致 GAS 執行逾時或記憶體溢位。
javascript
function writeContentInChunks(docBody, fullText) {
const CHUNK_SIZE = 20000;
docBody.setText(""); // 清空舊內容
for (let i = 0; i < fullText.length; i += CHUNK_SIZE) {
const chunk = fullText.substring(i, i + CHUNK_SIZE);
const paragraphs = docBody.getParagraphs();
const lastParagraph = paragraphs[paragraphs.length - 1];
if (lastParagraph) {
lastParagraph.appendText(chunk);
} else {
docBody.appendParagraph(chunk);
}
Utilities.sleep(150); // 避免 API Rate Limiting
}
}4. 部署與自動化
- Library 封裝:將核心邏輯部署為 Library,透過「指令碼 ID」在其他專案中引用,實現程式碼重用與解耦。
- 自動化觸發:點擊左側「觸發條件」,設定「時間驅動」觸發器(例如每小時執行一次
syncCloudyWingLog),即可達成背景自動同步。
技術選型總結
在選擇知識庫方案時,曾評估過以下工具,但因特定需求未達標而放棄:
- AnythingLLM:雖然功能強大,但需要進行向量索引 (Vector Indexing) 匯入,且參數(Chunk Size/Overlap)調校成本高,對於筆記搜尋的精準度要求難以滿足。
- 本地 AI 模型 (LM Studio/Ollama/KoboldCpp):雖然隱私性高,但受限於 VRAM 資源與硬體效能,且缺乏像 NotebookLM 那樣針對長文上下文的優化處理。
結論:對於無隱私疑慮的筆記,NotebookLM 仍是目前 RAG 效果與維護成本平衡點最高的選擇。透過 GAS 解決檔案同步問題後,可大幅降低手動維護的繁瑣度。
異動歷程
- 初版文件建立。